package com.mall.controller;


import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.bean.BeanUtil;
import com.alipay.api.AlipayApiException;
import com.aventrix.jnanoid.jnanoid.NanoIdUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.mall.DTO.WaresDTO;
import com.mall.common.Constant;
import com.mall.note.Limit;
import com.mall.pojo.Wares;
import com.mall.service.ICategoryService;
import com.mall.service.IOrderService;
import com.mall.service.IWaresService;
import com.mall.utils.InitDataUtil;
import com.mall.utils.R;
import com.mall.utils.ThreadLocalUtils;
import com.mall.vo.AddWareVo;
import com.mall.vo.UpdateWareVo;
import com.mall.vo.WareBuyVo;
import com.mall.vo.WareVo;
import jakarta.annotation.Resource;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 商品管理
 *
 * @author cxx
 */
@Slf4j
@RestController
@RequestMapping("/wares")
@SaCheckLogin
public class WaresController {
    @Value("${image.path}")
    private String imagePath;
    @Resource
    private IWaresService waresService;
    @Resource
    private InitDataUtil initDataUtil;
    @Resource
    private ICategoryService categoryService;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Resource
    private DefaultRedisScript<Long> decrScript;
    @Resource
    private IOrderService orderService;
    @Resource
    private RabbitTemplate rabbitTemplate;
    @Resource
    private ThreadLocalUtils threadLocalUtils;

    /**
     * 上传商品图片保存到服务器
     *
     * @param file 上传的图片文件
     * @return com.mall.utils.R<java.lang.String>
     * @author cxx
     * @date 2023/4/24 9:52
     */

    @SaCheckPermission("ware-update")
    @PostMapping("/upload")
    @CacheEvict(value = "Wares", allEntries = true)
    public R<String> upload(MultipartFile file) throws IOException {
        String filename = file.getOriginalFilename();
        assert filename != null;
        String suffix = filename.substring(filename.lastIndexOf("."));
        String prefix = NanoIdUtils.randomNanoId();
        file.transferTo(new File(imagePath + prefix + suffix));
        return R.success(prefix + suffix);
    }

    /**
     * 将服务器上的商品图片展示到浏览器中
     *
     * @param response
     * @param fileName 商品图片名字
     * @return void
     * @author cxx
     * @date 2023/4/24 9:51
     */
    @GetMapping("/download")
    @CacheEvict(value = "Wares", allEntries = true)
    public void download(HttpServletResponse response, @RequestParam("fileName") String fileName) {
        int count;
        try (FileInputStream stream = new FileInputStream(imagePath + fileName)) {
            ServletOutputStream outputStream = response.getOutputStream();
            byte[] bytes = new byte[1024];
            while ((count = stream.read(bytes)) != -1) {
                outputStream.write(bytes, 0, count);
            }
            outputStream.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 通过商品名称分页查询商品信息
     *
     * @param page     页号 可以不传，不传默认为1
     * @param pageSize 页码大小 可以不传，不传默认为5
     * @param wareName 查询的商品名称，可以为null，为null查询所有的商品
     * @return com.mall.utils.R<java.util.HashMap < java.lang.String, java.lang.Object>>
     * @author cxx
     * @date 2023/4/24 9:50
     */
    @Limit
    @SaCheckPermission("ware-query")
    @GetMapping("/list")
    @Cacheable(value = "Wares", unless = "#result.data.size()==0")
    public R<Object> list(@RequestParam(name = "page", required = false, defaultValue = "1") Integer page, @RequestParam(name = "pageSize", required = false, defaultValue = "5") Integer pageSize, @RequestParam(name = "wareName", required = false) String wareName) {
        Page<WaresDTO> waresDtoPage = waresService.getWaresWithclassName(page, pageSize, wareName);

        if (waresDtoPage.getTotal() == 0) {

            return R.error("不存在该商品", waresDtoPage.getRecords());
        }
        List<WareVo> collect = waresDtoPage.getRecords().stream().map(item -> {
            WareVo wareVo = new WareVo();
            BeanUtils.copyProperties(item, wareVo);

            return wareVo;
        }).collect(Collectors.toList());
        HashMap<String, Object> map = new HashMap<>(16);
        map.put("records", collect);
        map.put("total", waresDtoPage.getTotal());
        return R.success(map);
    }

    /**
     * 通过商品id获取商品信息以及商品所属类别
     *
     * @param wareId 商品id
     * @return com.mall.utils.R<com.mall.pojo.Wares>
     * @author cxx
     * @date 2023/6/2 9:19
     */
    @GetMapping("/getWareById")
    public R<WareVo> getWareById(@RequestParam(name = "wareId") Long wareId) {
        Wares ware = waresService.getById(wareId);
        WaresDTO wareDTO = new WaresDTO();
        BeanUtils.copyProperties(ware, wareDTO);
        wareDTO.setClassName(categoryService.getCategoryNameByWareId(ware.getId()));
        WareVo wareVo = new WareVo();
        BeanUtils.copyProperties(wareDTO, wareVo);
        return R.success(wareVo);

    }

    /**
     * 修改商品信息
     *
     * @param ware 商品的各种信息,包括商品的id,名字,分类,价格,数量，描述,分类名是通过下拉框的形式选择输入，数据从后端查询而来
     * @return com.mall.utils.R<java.lang.String>
     * @author cxx
     * @date 2023/4/24 9:48
     */
    @PutMapping("/update")
    @SaCheckPermission("ware-update")
    @CacheEvict(value = {"Wares", "CategoryWithWares"}, allEntries = true)
    public R<String> update(@RequestBody UpdateWareVo ware) {
        WaresDTO waresDTO = new WaresDTO();
        BeanUtil.copyProperties(ware, waresDTO);
        boolean b = waresService.updateWaresByIdWithclassName(waresDTO);
        initDataUtil.initData();
        return b ? R.success("修改成功!") : R.error("修改失败!");
    }

    /**
     * 新增商品
     *
     * @param ware 新增商品的各种信息
     * @return com.mall.utils.R<java.lang.String>
     * @author cxx
     * @date 2023/4/24 9:48
     */
    @PostMapping("/add")
    @SaCheckPermission("ware-save")
    @CacheEvict(value = {"Wares", "CategoryWithWares"}, allEntries = true)
    public R<String> add(@RequestBody AddWareVo ware) {
        WaresDTO waresDTO = new WaresDTO();
        BeanUtil.copyProperties(ware, waresDTO);
        boolean save = waresService.saveWaresWithclassName(waresDTO);
        initDataUtil.initData();
        return save ? R.success("添加成功!") : R.error("添加失败!");
    }

    /**
     * 批量删除商品
     *
     * @param wareIds 要删除的商品id集合
     * @return com.mall.utils.R<java.lang.String>
     * @author cxx
     * @date 2023/4/24 21:30
     */
    @DeleteMapping({"/delete/{wareIds}"})
    @SaCheckPermission("ware-delete")
    @CacheEvict(value = {"Wares", "CategoryWithWares"}, allEntries = true)
    public R<String> delete(@PathVariable List<Long> wareIds) {
        boolean b = waresService.removeBatchByIds(wareIds);
        initDataUtil.initData();
        return b ? R.success("删除成功!") : R.error("删除失败!");
    }

    /**
     * 商品购买，需要传递的是购买商品id，商品数量和备注(非必须)，返回的是沙箱支付的支付表单和总订单id
     *
     * @param list 要购买的商品的集合
     * @return 沙箱支付的支付表单
     * @author cxx
     * @date 2023/4/28 22:39
     */
    @PostMapping("/pay")
    @CacheEvict(value = {"successOrder", "expiredOrder"}, allEntries = true)
    public R<Map<String, Object>> pay(@RequestBody List<WareBuyVo> list) throws Exception {

        //检查购买数量是否超过库存量
        for (WareBuyVo ware : list) {
            //内存标记看是否商品充裕
            if (Constant.MEMORY_TAGS.get(ware.getId().toString())) {
                int count = Constant.count.incrementAndGet();
                log.error("失败次数:" + count);
                return R.error("商品数量不足!");
            } else {
                List<String> keys = Collections.singletonList(ware.getId() + "_num");
                log.info(redisTemplate.opsForValue().get(keys.get(0)).toString());
                //调用lua脚本实现库存预减,预减成功返回1失败返回0
                Long execute = redisTemplate.execute(decrScript, keys, ware.getNum());
                //商品数量不足
                if (Math.toIntExact(execute) == 0) {
                    Constant.MEMORY_TAGS.put(ware.getId().toString(), true);
                    log.error("商品" + ware.getId() + "库存不足");
                    int count = Constant.count.incrementAndGet();
                    log.error("失败次数:" + count);
                    return R.error("商品数量不足!");
                }
            }
        }
        List<WaresDTO> waresDTOList = list.stream().map(item -> {
            WaresDTO waresDTO = new WaresDTO();
            BeanUtils.copyProperties(item, waresDTO);
            return waresDTO;
        }).collect(Collectors.toList());
        //下订单
        String s = waresService.buyWareAndsaveOrderDetail(waresDTOList);
        //获取总订单id
        long orderId = (long) threadLocalUtils.get();
        //  线程清除操作,移除threadLocal值
        threadLocalUtils.remove();
        log.info("总订单id为:" + orderId);
        log.info("支付表单为:" + s);
        HashMap<String, Object> map = new HashMap<>(16);
        map.put("payForm", s);
        map.put("orderId", orderId);
        return R.success(map);
    }

    /**
     * 根据商品id抢购商品
     *
     * @param wareId 商品id
     * @return com.mall.utils.R<java.lang.String>
     * @author cxx
     * @date 2023/4/27 9:19
     */
    @GetMapping("/secKill")
    @CacheEvict(value = {"successOrder", "expiredOrder"}, allEntries = true)
    public R<String> secKill(@RequestParam(name = "wareId") Long wareId) throws JsonProcessingException, AlipayApiException {
        return waresService.secKillWareById(wareId);
    }

}
